package com.hbx.logs.es;

import com.hbx.config.properties.EsProperties;
import com.hbx.constant.OperaResultEnum;
import com.hbx.dto.LogSearchParamDto;
import com.hbx.exception.RspException;
import com.hbx.logs.es.doc.SmsLogs;
import com.hbx.logs.pojo.LogSmsInfo;
import com.hbx.logs.OperaTypeEnum;
import com.hbx.logs.SearchResp;
import com.hbx.utils.DateUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/*
 * @Author:hbx
 *
 * */
@Component
@Slf4j
public class EsTemplate {
    /**
     * 日志索引
     */
    private static final String RESOURCE_LOGS = "resource_logs";
    private static RestHighLevelClient client;

    @Autowired
    private ObjectMapper objectMapper;

    public EsTemplate(@Autowired EsProperties esProperties){
        try {
            CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(esProperties.getUsername(), esProperties.getPassword()));
            client = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost(esProperties.getHost(), esProperties.getPort())).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
                        @Override
                        public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
                            httpClientBuilder.disableAuthCaching();
                            return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                        }
                    }));
            log.info("【es模块】:获取client对象成功");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("【es模块】:获取client对象失败,{}",e.getMessage());
        }
    }

    public RestHighLevelClient getClient(){
        return client;
    }

    public String getResourceLogsIndex(){
        return RESOURCE_LOGS;
    }

    /**
     * 添加日志
     * @param logSmsInfo
     */
    public String addLogs(LogSmsInfo logSmsInfo){
        IndexRequest request = new IndexRequest(getResourceLogsIndex());
        SmsLogs smsLogs = new SmsLogs();
        BeanUtils.copyProperties(logSmsInfo,smsLogs);
        try {
            request.source(objectMapper.writeValueAsString(smsLogs),XContentType.JSON);
            client.index(request, RequestOptions.DEFAULT);
            return OperaResultEnum.OPERA_SUCCESS.getResult();
        } catch (JsonProcessingException e) {
            log.error("【es模块-添加日志】:添加失败,日志对象转json失败,{}",e.getMessage());
        } catch (IOException e) {
            log.error("【es模块-添加日志】:添加失败,{}",e.getMessage());
        }
        return OperaResultEnum.OPERA_ERROR.getResult();
    }

    /**
     * 条件查询
     * @param logDto
     * @return
     */
    public SearchResp getLogsCondition(LogSearchParamDto logDto) {
        SearchRequest request = new SearchRequest(getResourceLogsIndex());
        SearchSourceBuilder builder = new SearchSourceBuilder();
        SearchResp resp = new SearchResp();
        resp.setPageNum(logDto.getPageNum());
        resp.setPageSize(logDto.getPageSize());
        List<SmsLogs> resultList = new ArrayList<>();
        try {
            conditionHandler(logDto, builder);
            builder.sort("operateDate", SortOrder.DESC);
            request.source(builder);
            SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
            long total = searchResponse.getHits().getTotalHits().value;
            SearchHit[] dataHits = searchResponse.getHits().getHits();
            for (SearchHit hit : dataHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                SmsLogs smsLogs = objectMapper.readValue(objectMapper.writeValueAsString(sourceAsMap), SmsLogs.class);
                smsLogs.setLogId(hit.getId());
                resultList.add(smsLogs);
            }
            resp.setTotal(total);
            resp.setLogsList(resultList);
            log.info("【es模块-查询日志】:查询成功,共{}条数据",resultList.size());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("【es模块-查询日志】:查询失败,{}",e.getMessage());
        }
        return resp;
    }

    /**
     * 日期条件查询
     * @return
     */
    public SearchResp getLogsByDate(String start, String stop) {
        SearchRequest request = new SearchRequest(getResourceLogsIndex());
        SearchSourceBuilder builder = new SearchSourceBuilder();
        SearchResp resp = new SearchResp();
        List<SmsLogs> resultList = new ArrayList<>();
        try {
            builder.size(10000);
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("operateDate")
                    .gt(DateUtil.stringTimeToDate(start).getTime() + 8 * 60 * 60 * 1000)
                    .lte(DateUtil.stringTimeToDate(stop).getTime() + 8 * 60 * 60 * 1000);
            builder.query(rangeQueryBuilder);
            builder.sort("operateDate", SortOrder.DESC);
            request.source(builder);
            SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
            long total = searchResponse.getHits().getTotalHits().value;
            SearchHit[] dataHits = searchResponse.getHits().getHits();
            for (SearchHit hit : dataHits) {
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                SmsLogs smsLogs = objectMapper.readValue(objectMapper.writeValueAsString(sourceAsMap), SmsLogs.class);
                smsLogs.setLogId(hit.getId());
                resultList.add(smsLogs);
            }
            resp.setTotal(total);
            resp.setLogsList(resultList);
            log.info("【es模块-查询日志】:查询成功,共{}条数据",resultList.size());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("【es模块-查询日志】:查询失败,{}",e.getMessage());
        }
        return resp;
    }

    /**
     * 封装检索条件
     * @param dto
     * @param builder
     * @throws ParseException
     */
    private void conditionHandler(LogSearchParamDto dto, SearchSourceBuilder builder) throws ParseException {
        // 封装分页数据
        builder.from((dto.getPageNum() - 1) * dto.getPageSize());
        builder.size(dto.getPageSize());
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (StringUtils.hasLength(dto.getOperateor())){
            boolQuery.must(QueryBuilders.termQuery("operateor",dto.getOperateor()));
        }
        if (StringUtils.hasLength(dto.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("operateDesc",dto.getKeyword()));
        }
        if (StringUtils.hasLength(dto.getOperateResult())){
            // 获取操作结果
            String operaResult = OperaResultEnum.getOperaResult(dto.getOperateResult());
            if (StringUtils.hasLength(operaResult)){
                boolQuery.must(QueryBuilders.termQuery("operateResult",operaResult));
            }
        }
        if (StringUtils.hasLength(dto.getOperateType())){
            // 获取操作类型
            String operaType = OperaTypeEnum.getType(dto.getOperateType());
            if (StringUtils.hasLength(operaType)){
                boolQuery.must(QueryBuilders.termQuery("operateType", operaType));
            }
        }
        if (StringUtils.hasLength(dto.getOperateClassName())){
            boolQuery.must(QueryBuilders.termQuery("operateClassName",dto.getOperateClassName()));
        }
        if (StringUtils.hasLength(dto.getOperateMethodName())){
            boolQuery.must(QueryBuilders.termQuery("operateMethodName",dto.getOperateMethodName()));
        }
        if (dto.getOperateTables() != null && dto.getOperateTables().length > 0){
            boolQuery.must(QueryBuilders.termsQuery("operateTables",dto.getOperateTables()));
        }
        if (dto.getOperateDate() != null && dto.getOperateDate().length > 1){
            String[] dates = dto.getOperateDate();
            boolQuery.must(QueryBuilders.rangeQuery("operateDate").gt(DateUtil.stringTimeToDate(dates[0]).getTime() + 8 * 60 * 60 * 1000).lt(DateUtil.stringTimeToDate(dates[1]).getTime() + 8 * 60 * 60 * 1000));
        }
        builder.query(boolQuery);
    }

    /**
     * 删除指定范围的日志
     * @param dateTimeRange
     */
    public void clearLog(List<String> dateTimeRange) {
        DeleteByQueryRequest request = new DeleteByQueryRequest(getResourceLogsIndex());
        request.setQuery(
                QueryBuilders.rangeQuery("operateDate")
                        .gte(DateUtil.stringTimeToDate(dateTimeRange.get(0)))
                        .lte(DateUtil.stringTimeToDate(dateTimeRange.get(1))));
        try {
            client.deleteByQuery(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("{}", e);
            throw new RspException("删除失败");
        }
    }
}
